﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace MQTT.Test
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // 模拟连接请求(Socket封装了TCP协议的报文处理)
                //AddressFamily.InterNetwork: ipv4
                Socket socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socketClient.Connect("127.0.0.1", 1883); // 完成三次握手
                Console.WriteLine(">>> TCP连接通道已建立");

                {
                    // 组装请求连接的报文:
                    List<byte> bytes = new List<byte>();

                    // 固定头
                    // 直接计算好int类型
                    //bytes.Add(16);
                    //bytes.Add(31);
                    // 直接计算好整数转byte类型
                    //bytes.Add((byte)16);
                    //bytes.Add((byte)31);
                    // 直接计算好16进制
                    //bytes.Add(0x10);
                    //bytes.Add(0x1F);

                    // 可变头
                    // 协议名称长度(2B)
                    bytes.Add((byte)(4 / 256));
                    bytes.Add((byte)(4 % 256));
                    // 协议名称内容
                    bytes.AddRange(Encoding.ASCII.GetBytes("MQTT"));
                    // MQTT协议的版本号(1B)
                    bytes.Add(4);

                    // 连接标志 1100 0010
                    // 8位bit,从高位到低位分别是128,64,32,16,8,4,2,1
                    byte flag = 0;
                    // 0000 0000
                    // 1000 0000
                    // 0100 0000
                    // 0000 0010
                    // 添加用户名标记
                    flag = (byte)(flag | 128);
                    // 添加密码标记
                    flag = (byte)(flag | 64);
                    // 添加CleanSession标记
                    flag = (byte)(flag | 2);
                    bytes.Add(flag);

                    // 添加Keep Alive(2B)(客户端连接到服务器,15秒没有任何动作(发布/订阅)且没有心跳包,这时就会把客户端踢掉.如果这个时间有动作或心跳,时间会被重新记录)
                    bytes.Add((byte)(15 / 256));
                    bytes.Add((byte)(15 % 256));

                    // 载荷
                    // Client ID Length:2个字节.比如长度为4(小于255,第一个字节为0,第二个字节为4),2个字节二进制表示:0000 0000 0000 0100 --> 04
                    bytes.Add(0); // 第一个字节为0
                    bytes.Add(4); // 第二个字节为4
                                  // 两个字节处理方式:除以和取模256、BitConverter、位移
                                  // 假设Client ID长度为300,占2个字节(一个字节0-255),对应二进制就是0000 0001 0010 1100
                                  // bytes.Add((byte)(300 / 256)); // 除以256拿到高位的值
                                  // bytes.Add((byte)(300 % 256)); // 取模256拿到低位的值
                                  // 所以也可以这么写:
                                  // bytes.Add((byte)4 / 256);
                                  // bytes.Add((byte)4 % 256);

                    // Client ID内容:C002
                    bytes.AddRange(Encoding.ASCII.GetBytes("C002"));

                    // 用户名
                    String username = "admin";
                    // 用户名长度(2B)
                    bytes.Add((byte)(username.Length / 256));
                    bytes.Add((byte)(username.Length % 256));
                    // 用户名内容
                    bytes.AddRange(Encoding.ASCII.GetBytes(username));

                    // 密码
                    String pwd = "123456";
                    // 密码长度(2B)
                    bytes.Add((byte)(pwd.Length / 256));
                    bytes.Add((byte)(pwd.Length % 256));
                    // 密码内容
                    bytes.AddRange(Encoding.ASCII.GetBytes(pwd));


                    // 固定头直接计算好用bytes发送
                    // socketClient.Send(bytes.ToArray(), 0, bytes.Count, SocketFlags.None);

                    // 固定头,用<<计算好发送
                    byte[] len = new byte[] { (byte)bytes.Count };
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        // 消息类型是一个字节的高4位(4bit):比如:消息类型为:1.请求连接,需要转换为高4位:0001则使用:1 << 4 用1左移4位(位移后,后面的位置置为0)
                        // 1的二进制左移4位,0000 0001 << 4 结果为:0001 0000 高4位为1,则消息类型为请求连接
                        // Console.WriteLine(1 << 4);
                        // ============================================================================================
                        // 同理消息类型为:12.心跳请求,需要转换为高4位:1100则使用:12 << 4 用12左移4位 
                        // 12的二进制左移4位,0000 1100 << 4 结果为:1100 0000
                        // Console.WriteLine(12 << 4);
                        // ============================================================================================
                        // 又同理消息类型为:7.发布完成,需要转换为高4位:0111则使用:7 << 4 用7左移4位
                        // 7的二进制左移4位,0000 0111 << 4 结果为:0111 0000
                        // Console.WriteLine(7 << 4);

                        // 添加固定头
                        memoryStream.WriteByte(1 << 4);
                        // 可变报头长度+载荷内容长度
                        memoryStream.Write(len, 0, len.Length);
                        memoryStream.Write(bytes.ToArray(), 0, bytes.Count);

                        byte[] array = memoryStream.ToArray();

                        socketClient.Send(array, 0, array.Length, SocketFlags.None);
                    }
                }

                // 判断连接成功
                {
                    // 先取一个字节(消息类型)
                    byte[] result = new byte[1]; // 1.Header Flags
                    int len1 = socketClient.Receive(result, 0, result.Length, SocketFlags.None);
                    var resp1 = result[0];
                    // 响应报头:
                    // Header Flags: 0x20, Message Type: Connect Ack
                    // 0010.... = Message Type: Connect Ack(2)
                    // .... 0000 = Reserved: 0
                    // 0010 0000 高4位为消息类型,右移4位为:0000 0010,则为消息类型:2.连接确认
                    if (resp1 >> 4 == 2)
                    {
                        Console.WriteLine(">>> 接收到请求响应报文");

                        //报文:Msg Len: 2
                        //可变报头长度+载荷内容长度
                        byte[] result2 = new byte[1]; // 2.Msg Len
                        int len2 = socketClient.Receive(result2, 0, result2.Length, SocketFlags.None);

                        // 读取可变报头+载荷内容
                        // Acknowledge Flags: 0x00
                        // 0000 000. = Reserved: Not set
                        // .... ...0 = Session Present: Not set
                        // Return Code: Connection Accepted (0)
                        // 设置读取响应报文的长度:result2[0]
                        byte[] result3 = new byte[result2[0]]; // 3.Return Code
                        int len3 = socketClient.Receive(result3, 0, result2[0], SocketFlags.None);
                        // 读取出Return Code
                        if (result3[1] == 0)
                        {
                            Console.WriteLine(">>> MQTT连接成功!");

                            // 连接成功后发:心跳(间断不停的发送)
                            {
                                byte[] array = null;  
                                using (MemoryStream memoryStream = new MemoryStream())
                                {
                                    // 拼心跳报文
                                    // 消息类型为:12.心跳请求
                                    memoryStream.WriteByte(12 << 4); // 添加固定头
                                    memoryStream.Write(new byte[] { 0 }, 0, 1); // 可变头长度+载荷长度

                                    array = memoryStream.ToArray();

                                    Task.Run(async () =>
                                    {
                                        while (true)
                                        {
                                            // 2秒一次心跳包
                                            await Task.Delay(2000);
                                            // 发送心跳
                                            socketClient.Send(array, 0, array.Length, SocketFlags.None);
                                        }
                                    });

                                }
                            }
                        }
                    }
                }


            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }


            Console.ReadKey();
        }
    }
}
